/*
 *
 * Copyright (c) 2020 The Raptor Authors. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "utils/log.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef _WIN32
#include <Windows.h>
#include <processthreadsapi.h>
#else
#include <sys/syscall.h>
#include <unistd.h>
#endif

#include "utils/atomic.h"
#include "utils/time.h"
#include "utils/string.h"
#include "utils/sync.h"

namespace raptor {
namespace {

void log_default_print(LogArgument *args);

AtomicIntptr g_log_function((intptr_t)log_default_print);
AtomicInt32 g_min_level((int32_t)LogLevel::kDebug);
char g_level_char[4] = {'D', 'I', 'W', 'E'};
Mutex g_single_mtx;

#define RAPTOR_LOG_FORMAT "@%s.%06ld %7lu [%c] %s (%s:%d)\n"

void log_default_print(LogArgument *args) {
    char time_buffer[64] = {0};

    struct timeval now;
    gettimeofday(&now, NULL);
    time_t timer = now.tv_sec;

#ifdef _WIN32
    struct tm stm;
    if (localtime_s(&stm, &timer)) {
        strcpy(time_buffer, "error:localtime");
    }
#else
    struct tm stm;
    if (!localtime_r(&timer, &stm)) {
        strcpy(time_buffer, "error:localtime");
    }
#endif
    // "%F %T" 2020-05-10 01:43:06
    else if (0 == strftime(time_buffer, sizeof(time_buffer), "%F %T", &stm)) {
        strcpy(time_buffer, "error:strftime");
    }

    size_t msg_size = strlen(args->message) + 128;
    if (msg_size >= 2048) {
        char *output_text = NULL;
        raptor_asprintf(&output_text, RAPTOR_LOG_FORMAT, time_buffer,
                        now.tv_usec, // microseconds
                        args->threadid, g_level_char[static_cast<int>(args->level)], args->message,
                        args->file, args->line);

        // fprintf(stderr, "%s\n", output_text);
        OutputDebugStringA(output_text);

        free(output_text);
    } else {
        char buff[2048] = {0};
        snprintf(buff, sizeof(buff), RAPTOR_LOG_FORMAT, time_buffer,
                 now.tv_usec, // microseconds
                 args->threadid, g_level_char[static_cast<int>(args->level)], args->message,
                 args->file, args->line);

        // fprintf(stderr, "%s\n", buff);
        OutputDebugStringA(buff);
    }

    fflush(stderr);
}
} // namespace

void LogSetLevel(LogLevel level) { g_min_level.Store((int32_t)level); }

void LogSetPrintCallback(LogPrintCallback func) { g_log_function.Store((intptr_t)func); }

void LogFormatPrint(const char *file, int line, LogLevel level, const char *format, ...) {

    if (static_cast<int32_t>(level) < g_min_level.Load()) return;

    char *message = NULL;
    va_list args;
    va_start(args, format);

#ifdef _WIN32
    int ret = _vscprintf(format, args);
    va_end(args);
    if (ret < 0) {
        return;
    }

    size_t buff_len = (size_t)ret + 1;
    message         = (char *)malloc(buff_len);
    va_start(args, format);
    ret = vsnprintf_s(message, buff_len, _TRUNCATE, format, args);
    va_end(args);
#else
    if (vasprintf(&message, format, args) == -1) { // stdio.h
        va_end(args);
        return;
    }
#endif

    LogArgument tmp;
    tmp.file     = file;
    tmp.line     = line;
    tmp.level    = level;
    tmp.message  = message;
    tmp.threadid = static_cast<unsigned long>(GetCurrentThreadId());
    g_single_mtx.Lock();
    ((LogPrintCallback)g_log_function.Load())(&tmp);
    g_single_mtx.Unlock();
    free(message);
}

} // namespace raptor
